
import threading
import sys
from Memory.ContinuosAsignment.Gap import Gap
from Memory.MemoryManager import MemoryManager
from Exceptions.ProgramNotInMemoryException import ProgramNotInMemoryException
from Exceptions.NotEnoughMemoryException import NotEnoughMemoryException

class MMU(MemoryManager):
    lock = threading.RLock()
    def __init__(self, memory, cpu):
        self.memory = memory
        self.cpu = cpu
        #Al crearse MMU se crea en la lista de huecos, 1 solo hueco que es el tamanio de toda la memoria
        self.gaps = [Gap(self.memory.length, 0)]
        #Para llevar referencia si el programa es el ultimo o no de la lista
        self.programs = [] 
        
    #Politica que retorna el primer hueco que encuentra de tamanio mayor o igual al size pasado por parametro   
    def returnBlockPrimerAjuste(self, aSize):
        for i in self.gaps:
            if(i.getSize() >= aSize):
                return i #self.blocks.index(i)
        return None
    #Politica que retorna el mejor hueco que encuentra de tamanio mayor o igual al size pasado por parametro
    def returnBlockMejorAjuste(self, aSize):
        mejorAjuste = self.returnBlockPrimerAjuste(aSize)
        for i in self.gaps:
            if(i.getSize() >= aSize and i.getSize() < mejorAjuste.getSize()):
                mejorAjuste = i
        return mejorAjuste
    
    #Politica que retorna el peor hueco que encuentra de tamanio mayor o igual al size pasado por parametro
    def returnBlockPeorAjuste(self, aSize):
        peorAjuste = self.returnBlockPrimerAjuste(aSize)
        for i in self.gaps:
            if(i.getSize() >= aSize and i.getSize() > peorAjuste.getSize()):
                peorAjuste = i
        return peorAjuste
        
    #   
    def swappIn(self, aProgram, pcb):
        self.lock.acquire()
        try:
            #Condicion de corte de recursion
            #Si la lista de huecos tiene 1 solo y el tamanio es menor al pedido, exception sin memoria
            if(len(self.gaps) == 1 and (self.gapPosition(0).getSize() < aProgram.size())):
                raise NotEnoughMemoryException()
            else:
                #Se obtiene un hueco libre de acuerdo a la politica primer ajuste
                gap = self.returnBlockPrimerAjuste(aProgram.size())
                gap.initCelda = self.positionContEmptyCells(aProgram.size())
                #Si existe un hueco en el que el programa entra continuamente, es cargado en memoria
                if(gap is not None):
                    self.memory.load(aProgram, gap.initCelda)
                    self.programs.append(aProgram)
                    pcb.baseRegistry = gap.initCelda
                    #Setea el tamanio de hueco restandole el tamanio del programa
                    gap.setSize(gap.getSize() - aProgram.size())
                #Si no existe un hueco para alojar el programa, compacta la memoria y se llama recursivamente
                else:
                    self.compact()
                    self.swappIn(aProgram)
        finally:
            self.lock.release()    
                
    def swappOut(self, aProgram, pcb):
        self.lock.acquire()
        try:
            if(self.programs.__contains__(aProgram)):
                #Si el programa no es el ultimo de la lista de programas, se agrega un hueco en la
                #lista de huecos con el tamanio del programa
                if(not self.isLast(aProgram)):
                    self.gaps.append(Gap(len(aProgram.getInstructionList()))) #No tendria que agregar al final
                #Si es el ultimo hueco de la lista, no tiene que agregar otro, sino cambiarle el tamanio
                #sumandole el tamanio del programa
                else:
                    lastGap = self.gaps.pop()
                    self.gaps.append(Gap(lastGap.getSize() + aProgram.size()))
                #Saca el programa de memoria 
                self.memory.unLoad(aProgram, pcb.baseRegistry)
                self.programs.remove(aProgram)
            else:
                #EXCEPTION !!!!
                raise ProgramNotInMemoryException(aProgram)
        finally:
            self.lock.release()
    
    #Metodo que devuelve true si la memoria esta llena    
    def memoryIsFull(self):
        return (self.memory.isFull())
    
    #Devuelve true si el programa es el ultimo de la lista de programas, esto lo necesito saber
    #poruqe si es el iltimo, al sacarlo de memoria no tengo que agregarle un hueco mas, sino agrandarlo
    #si no es el ultimo agrego un hueco con el tamanio del programa
    def isLast(self, aProgram):
        #Clono la lista para que no haga referencia al mismo objeto y al hacerle
        #pop() no se modifiquen ambas
        progList = self.programs[:]
        return (progList.pop() == aProgram)

    #Compacta la memoria, crea un solo hueco en la lista de huecos con el tamanio de todos los huecos
    #que se encontraban anteriormente
    def compact(self):
        totalSize = 0        
        for i in self.gaps:
            totalSize = totalSize + i.getSize()
        self.gaps = [Gap(totalSize)]
        
    def gapPosition(self, aPosition):
        return self.gaps[aPosition]
        
    def readMemory(self, pcb):
        return self.memory.read(pcb.baseRegistry + pcb.currentInstruction).instruction
    
    
    #Metodo que devuelve la posicion inicial de memoria en donde el tamanio pasado por parametro
    #entre de forma continua    
    def positionContEmptyCells(self, aSize):
        iter = 0
        freeCellsCont = 0
        initPos = 0
        while(freeCellsCont < aSize):
            actualCell = self.memory.list.__getitem__(iter)
            if(actualCell.instruction == None):
                freeCellsCont = freeCellsCont + 1
            else:
                initPos = actualCell.position + 1
                freeCellsCont = 0
            iter = iter + 1
                
        return initPos
